47. 创建N维数组

本章概要

  • 学习材料:一维/二维数组、轴定义与价格序列。
  • 本章任务:运行 lst-array-attributeslst-array-statslst-returns-calculation,完成shape/dtype、axis统计和收益。
  • 完成后你将得到:数组shape/dtype、行列统计与收益/累计收益序列。
  • 自我检查:用显式公式复算首个收益并检查shape;非正价格或轴长度错位时先检查原因。
  • 拓展练习:把数组分析应用到另一中国指数。

本章核心:NumPy数组是数据分析的基石

  • NumPy(Numerical Python)是Python科学计算的核心库
  • 其核心数据结构是 ndarray(N-dimensional array,N维数组)
  • 相比Python列表,NumPy数组在内存效率计算性能上具有巨大优势
  • 本章学习:创建一维/二维数组、特殊数组、数组属性与运算

张量(Tensor)与多维数组的关系

从数学角度,NumPy数组实现了张量的概念:

维度 数学名称 示例
0维 标量(Scalar) \(5\)
1维 向量(Vector) \([1, 2, 3]\)
2维 矩阵(Matrix) \([[1,2],[3,4]]\)
3维+ 高维张量 时间序列数据立方体

对于 \(d\) 维张量 \(\mathbf{A}\),元素索引为 \(\mathbf{A}_{i_1, i_2, \ldots, i_d}\)

NumPy数组 vs Python列表

特性 NumPy数组 Python列表
内存布局 连续内存块 分散的对象引用
数据类型 同构(类型相同) 异构(可混合)
内存效率 紧凑(int32占4字节) 每个对象约28字节
计算性能 向量化(C层面) 解释执行(Python层面)

NumPy快的秘密:向量化运算 + C语言底层实现

为什么NumPy如此重要?

  • 向量化运算:避免Python循环,利用CPU的SIMD指令
  • 广播机制:自动对齐不同形状的数组
  • C语言底层:核心代码用C实现,速度接近编译语言
  • 丰富的函数库:线性代数、傅里叶变换、随机数生成等

运行前预测|平台任务解答代码

  • 输入预测:运行前先写出 price_amazonprice_app1eprice_appledata1 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。
  • 结果预测:不展开答案,先预测将得到one_array2 的结果;同时写出方向、数量级或表格/图形结构。
  • 完成要求:能独立说明本任务从输入到“平台任务解答代码”结果的关键步骤,原样录入平台代码并得到可核对的运行结果。

⭐ 平台任务解答代码

展开完整代码(投影默认折叠)
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#任务一
import numpy as np #导入NumPy模块并且用英文缩写np

price_amazon= np.array([170.23,170.10,177.59,177.06,178.22])    #直接创建一维数组

type (price_amazon)  # 查看price_amazon的数据类型

price_app1e=[221.27,221.72,224.72,226.05,225.89]  # 定义列表price_app1e

price_apple=np.array(price_app1e)                 #将列表转换为一维数组 

type(price_apple)  # 查看price_apple的数据类型

#任务二
import numpy as np

data1 = [170.23,170.10,177.59,177.06,178.22]  # 定义列表data1
data2 = [221.27,221.72,224.72,226.05,225.89]  # 定义列表data2
data3 = [413.26,416.11,421.03,418.47,421.53]  # 定义列表data3
data4 = [164.16,160.37,161.30,162.96,166.67]  # 定义列表data4
data5 = [648.02,661.68,663.22,674.07,688.53]  # 定义列表data5

price_array=np.array([data1,data2,data3,data4,data5]) #将以上列表数据创建成数组

print(price_array.shape)  #查看数组的形状
print(price_array.ndim)  #查看数组的维度
print(price_array.size)  #查看数组的元素个数
print(price_array.dtype)  #查看数组中的元素类型
  

#任务三
import numpy as np

price_app1e=[221.27,221.72,224.72,226.05,225.89]  # 定义列表price_app1e

n=price_array.size  #变量n赋值为数组的元素个数

data1 = [170.23,170.10,177.59,177.06,178.22]  # 定义列表data1
data2 = [221.27,221.72,224.72,226.05,225.89]  # 定义列表data2
data3 = [413.26,416.11,421.03,418.47,421.53]  # 定义列表data3
data4 = [164.16,160.37,161.30,162.96,166.67]  # 定义列表data4
data5 = [648.02,661.68,663.22,674.07,688.53]  # 定义列表data5

price_array=np.array([data1,data2,data3,data4,data5]) #将以上列表数据创建成数组

n=price_array.size  #变量n赋值为数组的元素个数

x=np.arange(n+1)  #生成整数序列
m=40  # 设置数组长度参数为40
y=np.linspace(price_apple[0],price_apple[-1],m)#生成起始值是苹果公司股票5月13日收盘价、终止值是5月17日收盘价且元素数量40的等差序列
print(y)  # 输出变量y的值

#任务四
import numpy as np

price_app1e=[221.27,221.72,224.72,226.05,225.89]  # 定义列表price_app1e

price_amazon=np.array([1822.68,1840,12,1871.15,1907.57,1869.00])  # 创建NumPy数组price_amazon

zero_arrayl=np.zeros_like(price_amazon)#快速生成元素为零的一维数组
zero_array2=np.zeros_like(price_array)#快速生成元素为零的二维数组
one_arrayl=np.ones_like(price_amazon)#快速生成元素等于1的一维数组
one_array2=np.ones_like(price_array) #快速生成元素等于1的二维数组
print(zero_arrayl)  # 输出变量zero_arrayl的值
print(zero_array2)  # 输出变量zero_array2的值
print(one_arrayl)  # 输出变量one_arrayl的值
print(one_array2)  # 输出变量one_array2的值

任务复盘|平台任务解答代码

运行后核对:核对 price_amazonprice_app1eprice_appledata1 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。

拓展练习:只改变一个关键输入或业务场景,先预测输出如何变化,再运行验证并解释变化原因。

创建一维数组:三种基本方法

Listing 1: 创建一维NumPy数组
import numpy as np

# 方法1:从Python列表创建
arr1 = np.array([1, 2, 3, 4, 5])

# 方法2:arange() — 指定步长生成序列
arr2 = np.arange(10)  # [0, 1, 2, ..., 9]

# 方法3:linspace() — 指定元素个数生成等间距数列
arr3 = np.linspace(0, 1, 5)  # [0., 0.25, 0.50, 0.75, 1.00]

print('arange:', arr2)
print('linspace:', arr3)
arange: [0 1 2 3 4 5 6 7 8 9]
linspace: [0.   0.25 0.5  0.75 1.  ]

arange() vs linspace() 的核心区别

函数 参数含义 是否包含终止值
np.arange(start, stop, step) 指定步长 不包含
np.linspace(start, stop, num) 指定元素个数 包含
# arange:不包含终止值1.0
np.arange(0, 1, 0.2)    # [0., 0.2, 0.4, 0.6, 0.8]

# linspace:包含终止值1.0
np.linspace(0, 1, 6)    # [0., 0.2, 0.4, 0.6, 0.8, 1.0]

数据类型的自动推断与显式指定

# NumPy自动推断数据类型
int_arr = np.array([1, 2, 3])
print(int_arr.dtype)     # int64

float_arr = np.array([1.0, 2.0, 3.0])
print(float_arr.dtype)   # float64

# 显式指定数据类型
arr_f32 = np.array([1, 2, 3], dtype=np.float32)
print(arr_f32.dtype)     # float32
  • float32float64 节省一半内存
  • 金融计算推荐 float64:精度更高,避免累积误差

创建二维数组:两种常用方法

Listing 2: 创建二维NumPy数组
展开实现代码
import numpy as np

# 方法1:从列表的列表创建
arr2d = np.array([
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
])

# 方法2:先创建一维数组,再reshape重塑
arr_3x4 = np.arange(12).reshape(3, 4)

print('2D数组(3x3):')
print(arr2d)

print(f'\n3x4数组:')
print(arr_3x4)
2D数组(3x3):
[[1 2 3]
 [4 5 6]
 [7 8 9]]

3x4数组:
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]

reshape() 的核心规则

  • 元素总数必须匹配3×4=12 的数组只能重塑为元素总数为12的形状
  • 使用 -1 自动推断:让NumPy自动计算某一维的大小
arr = np.arange(12)   # 12个元素
arr.reshape(3, 4)     # 3×4=12 ✓
arr.reshape(4, 3)     # 4×3=12 ✓
arr.reshape(3, -1)    # 自动推断为(3, 4)
arr.reshape(-1, 4)    # 自动推断为(3, 4)
# arr.reshape(5, 3)   # 5×3=15 ✗ 错误!

数组形状与元素访问

arr = np.array([[1, 2, 3], [4, 5, 6]])
print(arr.shape)   # (2, 3) — 2行3列

# 访问特定位置的元素
print(arr[0, 1])   # 2 — 第0行,第1列
print(arr[1, 2])   # 6 — 第1行,第2列
  • shape 返回元组,表示各维度的大小
  • 索引从 0 开始

创建特殊数组

Listing 3: 创建特殊NumPy数组
展开实现代码
import numpy as np

# 全0数组
zeros = np.zeros((2, 3))
print('全0数组(2x3):')
print(zeros)

# 全1数组
ones = np.ones((3, 4))
print('\n全1数组(3x4):')
print(ones)

# 单位矩阵(对角线为1,其余为0)
identity = np.eye(3)
print('\n单位矩阵(3x3):')
print(identity)
全0数组(2x3):
[[0. 0. 0.]
 [0. 0. 0.]]

全1数组(3x4):
[[1. 1. 1. 1.]
 [1. 1. 1. 1.]
 [1. 1. 1. 1.]]

单位矩阵(3x3):
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]

对角矩阵与随机数组

Listing 4: 对角矩阵与随机数组
import numpy as np

# 对角矩阵
diag = np.diag([1, 2, 3])
print('对角矩阵:')
print(diag)

# 标准正态分布随机数组(均值0,标准差1)
random_arr = np.random.randn(2, 3)
print('\n随机数组(2x3):')
print(random_arr)
对角矩阵:
[[1 0 0]
 [0 2 0]
 [0 0 3]]

随机数组(2x3):
[[-0.46157052 -0.05331524 -0.28077034]
 [-1.24771072  1.53803089 -0.56827971]]

特殊数组的数学意义

数组 函数 数学性质
零矩阵 np.zeros() \(O + A = A\)
全1矩阵 np.ones() 常用于广播运算
单位矩阵 np.eye() \(I \cdot A = A \cdot I = A\)
对角矩阵 np.diag() 协方差矩阵的简化形式

zeros_like / ones_like:快速生成同形数组

price_amazon = np.array([170.23, 170.10, 177.59])

# 生成与原数组形状和类型相同的全0/全1数组
zero_arr = np.zeros_like(price_amazon)
one_arr = np.ones_like(price_amazon)

print(zero_arr)  # [0. 0. 0.]
print(one_arr)   # [1. 1. 1.]
  • 保持与原数组相同的形状和数据类型
  • 适合初始化与已有数组结构一致的新数组

数组的四大核心属性

Listing 5: NumPy数组属性与形状操作
展开实现代码
import numpy as np

arr = np.array([[1, 2, 3], [4, 5, 6]])

print('形状 shape:', arr.shape)      # (2, 3)
print('维度 ndim:', arr.ndim)        # 2
print('元素总数 size:', arr.size)    # 6
print('数据类型 dtype:', arr.dtype)  # int64

# reshape改变形状
arr_reshaped = arr.reshape(3, 2)
print('\n重塑后(3x2):')
print(arr_reshaped)
形状 shape: (2, 3)
维度 ndim: 2
元素总数 size: 6
数据类型 dtype: int64

重塑后(3x2):
[[1 2]
 [3 4]
 [5 6]]

维度(ndim)的直观理解

ndim 结构 Python创建方式
0 标量 np.array(5)
1 向量 np.array([1,2,3])
2 矩阵 np.array([[1,2],[3,4]])
3 张量 np.array([[[1,2],[3,4]],[[5,6],[7,8]]])
  • 维度决定了数据的组织方式
  • 不同维度的操作对应不同的 axis 参数

展平数组:ravel() vs flatten()

arr = np.array([[1, 2, 3], [4, 5, 6]])

# ravel() 返回视图(view)— 修改会影响原数组
flat_view = arr.ravel()

# flatten() 返回副本(copy)— 修改不影响原数组
flat_copy = arr.flatten()
  • ravel():性能更好,但注意共享内存
  • flatten():更安全,修改互不影响

数组的元素级运算

Listing 6: NumPy数组向量化运算
import numpy as np

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

# 元素级运算(对应位置分别计算)
print('加法:', a + b)     # [5 7 9]
print('减法:', a - b)     # [-3 -3 -3]
print('乘法:', a * b)     # [4 10 18](注意:不是矩阵乘法!)
print('除法:', a / b)     # [0.25 0.4 0.5]
print('幂运算:', a ** 2)  # [1 4 9]
加法: [5 7 9]
减法: [-3 -3 -3]
乘法: [ 4 10 18]
除法: [0.25 0.4  0.5 ]
幂运算: [1 4 9]

数组的统计运算

Listing 7: 数组统计函数
import numpy as np

a = np.array([1, 2, 3])

print(f'均值: {a.mean():.2f}')    # 2.00
print(f'标准差: {a.std():.2f}')   # 0.82
print(f'方差: {a.var():.2f}')     # 0.67
print(f'最大值: {a.max()}')       # 3
print(f'最小值: {a.min()}')       # 1
print(f'求和: {a.sum()}')         # 6
均值: 2.00
标准差: 0.82
方差: 0.67
最大值: 3
最小值: 1
求和: 6

广播机制(Broadcasting)

不同形状的数组也可以运算——NumPy自动扩展较小的数组:

# 标量与数组
5 + np.array([1, 2, 3])  # [6, 7, 8]

# (3,1) 与 (3,) 的广播
arr1 = np.array([[1], [2], [3]])  # shape: (3,1)
arr2 = np.array([10, 20, 30])    # shape: (3,)
result = arr1 + arr2
# [[11, 21, 31],
#  [12, 22, 32],
#  [13, 23, 33]]

元素乘法 vs 矩阵乘法

a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])

# 元素乘法(*):对应位置相乘
a * b    # [[ 5, 12], [21, 32]]

# 矩阵乘法(@):线性代数的矩阵乘法
a @ b    # [[19, 22], [43, 50]]
  • * → 元素级(element-wise)
  • @np.dot() → 矩阵乘法(线性代数)

axis参数:沿哪个方向计算?

arr = np.array([[1, 2, 3],
                [4, 5, 6],
                [7, 8, 9]])

arr.mean()         # 5.0 — 全局均值
arr.mean(axis=0)   # [4., 5., 6.] — 沿列方向(↓)
arr.mean(axis=1)   # [2., 5., 8.] — 沿行方向(→)
  • axis=0:沿方向压缩(对每操作)
  • axis=1:沿方向压缩(对每操作)

布尔索引:按条件筛选元素

arr = np.array([1, 2, 3, 4, 5])

# 比较运算返回布尔数组
mask = arr > 3
print(mask)          # [False, False, False, True, True]

# 使用布尔索引筛选
filtered = arr[mask]
print(filtered)      # [4, 5]
  • 先用条件生成布尔掩码
  • 再用掩码筛选满足条件的元素

金融应用:计算收益率

Listing 8: 使用NumPy计算收益率
展开实现代码
import numpy as np

# 5个交易日的收盘价
prices = np.array([100, 105, 103, 108, 106])

# 简单收益率:R_t = (P_t - P_{t-1}) / P_{t-1}
simple_returns = (prices[1:] - prices[:-1]) / prices[:-1]

# 对数收益率:r_t = ln(P_t / P_{t-1})
log_returns = np.log(prices[1:] / prices[:-1])

print('简单收益率:')
print(simple_returns)
print('\n对数收益率:')
print(log_returns)
简单收益率:
[ 0.05       -0.01904762  0.04854369 -0.01851852]

对数收益率:
[ 0.04879016 -0.01923136  0.04740224 -0.01869213]

简单收益率 vs 对数收益率

类型 公式 特点
简单收益率 \(R_t = \frac{P_t - P_{t-1}}{P_{t-1}}\) 直观,但不可跨期相加
对数收益率 \(r_t = \ln\frac{P_t}{P_{t-1}}\) 时间可加性\(r_{total} = r_1 + r_2\)
  • 对数收益率在统计分析中更常用
  • 当收益率较小时,两者近似相等

累计收益率的计算

Listing 9: 累计收益率计算
展开实现代码
import numpy as np

prices = np.array([100, 105, 103, 108, 106])
simple_returns = (prices[1:] - prices[:-1]) / prices[:-1]

# 累计收益率 = (1+R_1) × (1+R_2) × ... × (1+R_n) - 1
cum_returns = (1 + simple_returns).cumprod() - 1

print('累计收益率:')
print(cum_returns)
print(f'\n总收益率: {cum_returns[-1]:.4f}')
累计收益率:
[0.05 0.03 0.08 0.06]

总收益率: 0.0600
  • cumprod() 计算累积乘积
  • 初始100元 → 经过5天 → 最终价值可直接由累计收益率推算

最佳实践总结

  • 数据类型:金融计算优先 float64,大数据集可用 float32
  • 向量化运算:避免Python循环,使用NumPy内置函数
  • 广播机制:利用自动形状对齐简化代码
  • 内存优化:用 reshape(-1) 自动推断,用 ravel() 避免复制
  • 数值稳定:用 np.log1p(x) 代替 np.log(1+x) 提高小数值精度

随堂练习

  • 问题 1|需要准备哪些数据?:一维/二维数组、轴定义与价格序列。
  • 问题 2|需要完成哪些操作?:运行 lst-array-attributeslst-array-statslst-returns-calculation,完成shape/dtype、axis统计和收益。
  • 问题 3|应得到哪些结果?:数组shape/dtype、行列统计与收益/累计收益序列。
  • 问题 4|怎样确认结果可靠?:用显式公式复算首个收益并检查shape;非正价格或轴长度错位时先检查原因。
  • 问题 5|换一个情境,怎样继续应用?:把数组分析应用到另一中国指数。
  • 作答提示:请依次写清所用数据、分析过程、所得结果、核对方法和拓展思考。课程所需数据见前言中的下载入口;教学平台固定题按页面说明完成。

教师参考解答|答案与说明 1

  • 所用数据与字段:二维价格矩阵 (2资产,3日期)float64;缺失掩码同形布尔数组。

教师参考解答|代码 1

展开代码(代码区可独立滚动)
import numpy as np  # 导入数组工具以完成矩阵运算与数值检查
prices=np.array([[10.,10.2,10.1],[20.,19.8,20.4]],dtype=np.float64)  # 建立价格或数组教学示例
zeros=np.zeros_like(prices); ones=np.ones_like(prices)  # 建立价格或数组教学示例
missing_mask=np.zeros_like(prices,dtype=bool); missing_mask[1,1]=True  # 建立价格或数组教学示例
adjusted=np.where(missing_mask,np.nan,prices)  # 建立价格或数组教学示例
assert zeros.shape==ones.shape==prices.shape==missing_mask.shape  # 建立价格或数组教学示例
assert zeros.dtype==prices.dtype and missing_mask.dtype==bool  # 建立价格或数组教学示例
print(prices.shape,prices.dtype,zeros,ones,adjusted)  # 建立价格或数组教学示例

教师参考解答|答案与说明 2

  • 解释答案:数组创建时 dtype 与 shape 决定可表达的值和后续轴运算;reshape 只改变视图结构,不自动赋予新的业务含义。
  • 拓展应用答案:拓展应用到门店×月份销量矩阵时,替换二维输入,声明两条轴并核对元素总数、行列合计和目标 dtype。
  • 参考结果:shape/dtype、同形0/1数组与一个缺失位置。另行核对:逐维检查 ndim/shape/sizesize==shape 乘积。
  • 常见错误:维度顺序反;整数数组写NaN;认为 empty_like 自动清零。